/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.dao.order;

import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.martinimix.beans.bind.BindingService;
import net.martinimix.beans.bind.BindingServiceAware;
import net.martinimix.dao.BusinessActionResponseMapper;
import net.martinimix.dao.BusinessActionTemplate;
import net.martinimix.dao.SimpleBusinessActionTemplate;
import net.martinimix.domain.account.Customer;
import net.martinimix.domain.order.Cart;
import net.martinimix.domain.order.Order;
import net.martinimix.domain.order.OrderHeader;
import net.martinimix.domain.order.PriceList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.bluemartini.dna.BusinessObject;
import com.bluemartini.dna.BusinessObjectArray;
import com.bluemartini.dna.DNAList;
import com.bluemartini.webconnect.WebConnect;

/**
 * Provides a Blue Martini order data access object.
 * 
 * @author Scott Rossillo
 *
 */
public class BlueMartiniOrderDao extends AbstractPrototypeAwareOrderDao implements OrderDao, BindingServiceAware {
	
	private final static Log log = LogFactory.getLog(BlueMartiniOrderDao.class);

	/**
	 * The binding service for this order dao. 
	 */
	private BindingService bindingService;
	
	/**
	 * The custom order data provider for this order dao.
	 */
	private OrderDataProvider orderDataProvider;
	
	
	/* (non-Javadoc)
	 * @see net.martinimix.dao.order.OrderDao#calculateTaxRate(net.martinimix.domain.order.Order)
	 */
	public double calculateTaxRate(Order order) {
		
		return orderDataProvider != null ? orderDataProvider.calculateTaxRate(order) : 0D;
	}

	/* (non-Javadoc)
	 * @see net.martinimix.dao.order.OrderDao#createOrder(net.martinimix.domain.account.Customer, net.martinimix.domain.order.Cart, net.martinimix.domain.order.PriceList)
	 */
	public Order createOrder(Customer customer, Cart cart, PriceList priceList) {

		Order order = this.createOrderBean();
		
		BusinessActionTemplate template = new BusinessActionTemplate(bindingService);
		BusinessActionResponseMapper mapper = new BusinessActionResponseMapper() {

			public Map map(DNAList response) {
				
				Map map = new LinkedHashMap();
				BusinessObject boOrder = 
					response.removeBusinessObject(WebConnect.SESSION_ORDER);
				
				BusinessObjectArray boOrderLines = 
					response.removeBusinessObjectArray(WebConnect.SESSION_ORDER_LINE_ARRAY);
				
				map.put("orderHeader", bindingService.bindBean(boOrder, createOrderHeaderBean()));
				map.put("orderLines", bindingService.bindBeans(boOrderLines, createOrderLineBean()));
				
				return map;
			}
		};
		
		Map input = new LinkedHashMap();
		
		input.put("PRICE_LIST", priceList);
		input.put(WebConnect.SESSION_CURRENT_USER, customer);
		input.put(WebConnect.SESSION_CART_HEADER, cart.getCartHeader());
		input.put(WebConnect.SESSION_CART_ITEM_ARRAY, cart.getCartItems());
		
		Map response = template.execute("WebConnectCreateOrder", input, mapper);
		OrderHeader header = (OrderHeader) response.get("orderHeader");
		
		order.setCustomer(customer);
		order.setOrderLines( (List) response.get("orderLines"));
		
		OrderHeader orh = createOrderNumber(header);
		
		header.setOrderNumber(orh.getOrderNumber());
		header.setId(orh.getId());
		
		if(orderDataProvider != null) {
			header.setExternalOrderNumber(orderDataProvider.createExternalOrderNumber());
		}
		
		log.debug("Setting order number to: " + orh.getOrderNumber());
		log.debug("Setting order id to:     " + orh.getId());
		
		order.setOrderHeader(header);
		saveOrderHeader(header, customer, true);  // saved for payment functionality constraints
		
		return order;
	}
	
	/**
	 * Creates a new Blue Martini order number.
	 * 
	 * @param orderHeader the <code>OrderHeader</code> for which a new order
	 * number should be created
	 * 
	 * @return the <code>orderHeader</code> with its order number set
	 */
	protected OrderHeader createOrderNumber(OrderHeader orderHeader) {
		
		SimpleBusinessActionTemplate template = new SimpleBusinessActionTemplate(bindingService);
		Map input = new LinkedHashMap();
		
		OrderHeader response;
		
		input.put(WebConnect.SESSION_ORDER, orderHeader);
		response = (OrderHeader) template.execute(
				"WebConnectGenerateOrderNumber", 
				this.createOrderHeaderBean(), WebConnect.SESSION_ORDER, input);
		
		return response;
	}

	
	/* (non-Javadoc)
	 * @see net.martinimix.dao.order.OrderDao#findOrder(java.lang.Long)
	 */
	public Order findOrder(Long orderId) {
			
		return null;
	}

	/* (non-Javadoc)
	 * @see net.martinimix.dao.order.OrderDao#saveOrder(net.martinimix.domain.order.Order, net.martinimix.domain.order.Cart)
	 */
	public void saveOrder(Order order, Cart cart) {
		
		FinalizeOrderActionChain chain = new FinalizeOrderActionChain(bindingService);
		
		validate(order);
		saveOrderHeader(order.getOrderHeader(), order.getCustomer(), false);
		chain.saveOrder(order, cart);
		
		order.getOrderHeader().setCreatedDate(new GregorianCalendar());
		order.getOrderHeader().setModifiedDate(new GregorianCalendar());
	}
	
	/**
	 * Saves the given order header.
	 * 
	 * @param orderHeader the <code>OrderHeader</code> to save
	 */
	protected void saveOrderHeader(OrderHeader orderHeader, Customer customer, boolean insert) {
		
		SimpleBusinessActionTemplate template = new SimpleBusinessActionTemplate(bindingService);
		Map input = new LinkedHashMap();
		
		if(insert) {
			orderHeader.setNewOrder(true);
			orderHeader.setIgnoreSequence(true);
		} else {
			orderHeader.setNewOrder(false);
			orderHeader.setIgnoreSequence(false);
		}
		
		if(orderHeader.getCreatedForCustomerId() == null) {
			orderHeader.setCreatedForCustomerId(customer.getId());
		}
		
		if(orderHeader.getCreatedBy() == null) {
			orderHeader.setCreatedBy(customer.getId());
		}
		
		if(orderHeader.getModifiedBy() == null) {
			orderHeader.setModifiedBy(customer.getId());
		}
		
		input.put("CURRENT_USER", customer);
		input.put("ORDER", orderHeader);
		
		template.executeWithoutOutput("StoreOrder", input, "ORDER");	
	}

	/* (non-Javadoc)
	 * @see net.martinimix.dao.bind.BindingServiceAware#setBindingService(net.martinimix.dao.bind.BindingService)
	 */
	public void setBindingService(BindingService bindingService) {
		this.bindingService = bindingService;
	}

	/**
	 * Sets the customer order data provider for this order dao.
	 * 
	 * @param orderDataProvider the <code>OrderDataProvider</code> for this
	 * dao
	 */
	public void setOrderDataProvider(OrderDataProvider orderDataProvider) {
		this.orderDataProvider = orderDataProvider;
	}
	
	/**
	 * Performs basic order validation.
	 * 
	 * @param order the <code>Order</code> to validate
	 * 
	 * @throws IllegalStateException if given <code>order</code> is not in
	 * a save-able state
	 */
	protected void validate(Order order) {
		
		if(order.getOrderHeader() == null) {
			throw new IllegalStateException("[OrderHeader] missing!");
		}
		
		if(order.getOrderLines() == null || order.getOrderLines().size() == 0) {
			throw new IllegalStateException("[Order] does not have any order lines!");
		}
		
	}

}
